﻿using System;
using System.IO;
using System.Threading.Tasks;
#if SILVERLIGHT
using System.Windows.Controls;
using System.Windows.Media;
#else
using System.Windows;
#endif
using System.Windows.Media.Imaging;

namespace FreshMeat.Xaml
{
   public static class ImageHelper
   {
      /// <summary>
      /// Creates a BitmapSource from raw image data.
      /// </summary>
      /// <param name="imageData">Image data.</param>
      /// <returns></returns>
      public static BitmapSource CreateSourceFrom(byte[] imageData)
      {
         using (var stream = new MemoryStream(imageData))
         {
            var source = new BitmapImage();
#if SILVERLIGHT
            source.SetSource(stream);
#else
            source.BeginInit();
            source.CacheOption = BitmapCacheOption.OnLoad; // load data now so the caller can dispose of the stream
            source.CreateOptions = BitmapCreateOptions.None;
            source.StreamSource = stream;
            source.EndInit();
#endif
            return source;
         }
      }

      /// <summary>
      /// Creates a BitmapSource from raw image data.
      /// </summary>
      /// <param name="imageData">Image data.</param>
      /// <param name="maxWidthOrHeight">If specified, image will be converted so it's largest dimension is this size while maintaining its original aspect ratio; if not specified, actual image size is used.</param>
      /// <param name="throwOnError">True throw an exception when an error occurs, false ignores errors and returns null.</param>
      /// <returns></returns>
      public static BitmapSource Load(byte[] imageData, int? maxWidthOrHeight, bool throwOnError)
      {
         try
         {
            using (var stream = new MemoryStream(imageData))
               return Load(stream, maxWidthOrHeight, throwOnError);
         }
         catch (Exception)
         {
            if (throwOnError)
               throw;

            return null;
         }
      }

      /// <summary>
      /// Creates an ImageSource from raw image data.
      /// </summary>
      /// <param name="imageData">Image data.</param>
      /// <param name="maxWidthOrHeight">If specified, image will be converted so it's largest dimension is this size while maintaining its original aspect ratio; if not specified, actual image size is used.</param>
      /// <param name="throwOnError">True throw an exception when an error occurs, false ignores errors and returns null.</param>
      /// <returns></returns>
      public static BitmapSource Load(Stream imageData, int? maxWidthOrHeight, bool throwOnError)
      {
         try
         {
#if SILVERLIGHT
            var source = new BitmapImage();
            source.SetSource(imageData);
            if (maxWidthOrHeight == null)
               return source;

            var scale = source.PixelWidth == 0 || source.PixelHeight == 0 ? 1.0 : source.PixelWidth > source.PixelHeight ? maxWidthOrHeight.Value / (double)source.PixelWidth : maxWidthOrHeight.Value / (double)source.PixelHeight;
            var image = new Image { Source = source };
            var bitmap = new WriteableBitmap(image, new ScaleTransform {CenterX = 0.5, CenterY = 0.5, ScaleX = scale, ScaleY = scale});
            bitmap.Invalidate();
            return bitmap;
#else
            var size = Size.Empty;
            if (maxWidthOrHeight != null)
            {
               // Read size (without loading entire image)
               size = ReadPixelSize(imageData);
               // Reset stream so we can read the image again
               imageData.Seek(0, SeekOrigin.Begin);
            }

            var bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.CacheOption = BitmapCacheOption.OnLoad; // load data now so the caller can dispose of the stream
            bitmap.CreateOptions = BitmapCreateOptions.None;
            if (maxWidthOrHeight != null)
            {
               if (size.Width > size.Height)
                  bitmap.DecodePixelWidth = maxWidthOrHeight.Value;
               else
                  bitmap.DecodePixelHeight = maxWidthOrHeight.Value;
            }
            bitmap.StreamSource = imageData;
            bitmap.EndInit();

            return bitmap;
#endif
         }
         catch (Exception)
         {
            if (throwOnError)
               throw;

            return null;
         }
      }

#if !SILVERLIGHT
      /// <summary>
      /// Gets an image's pixel size without loading the entire image.
      /// </summary>
      /// <param name="imageData"></param>
      /// <returns></returns>
      static Size ReadPixelSize(Stream imageData)
      {
         var test = new BitmapImage();
         test.BeginInit();
         test.CacheOption = BitmapCacheOption.Default; // don't load image data -- we just need the "header" stuff which it gets immediately
         test.StreamSource = imageData;
         test.EndInit();
         return new Size(test.PixelWidth, test.PixelHeight);
      }
#endif

      // TODO: Consider converting to JPG instead of PNG for greater compression - see http://programmerpayback.com/2010/01/21/use-silverlight-to-resize-images-and-increase-compression-before-uploading/
      /// <summary>
      /// Loads an image stream, resizing it as requested.
      /// The input and output data types were specifically chosen to avoid cross-threading issues on both WPF and Silverlight.
      /// </summary>
      /// <param name="file">File containing image data.</param>
      /// <param name="maxWidthOrHeight">If specified, image will be converted so it's largest dimension is this size while maintaining its original aspect ratio; if not specified, actual image size is used.</param>
      /// <param name="throwOnError">True throw an exception when an error occurs, false ignores errors and returns null.</param>
      /// <returns></returns>
      public static async Task<byte[]> ConvertToPng(FileInfo file, int? maxWidthOrHeight, bool throwOnError)
      {
         // For Silverlight this must run on the UI thread because it creates a BitmapSource which can only be done by that thread.
         BitmapSource image;
         using (var stream = file.OpenRead())
            image = Load(stream, maxWidthOrHeight, throwOnError);
         var encodable = PngEncoder.ToEncodable(image);

#if SILVERLIGHT
         return await TaskEx.Run(
#else
         return await Task.Run(
#endif
            () =>
            {
               var pngStream = PngEncoder.Encode(encodable);
               return pngStream.ToArray();
            });
      }
   }
}
